﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace System
{
    public static class TypeExtentions
    {
        private class PairOfTypes
        {
            private readonly Type first;
            private readonly Type second;
            public PairOfTypes(Type first, Type second)
            {
                this.first = first;
                this.second = second;
            }
            public override int GetHashCode() { return 31 * first.GetHashCode() + second.GetHashCode(); }
            public override bool Equals(object obj)
            {
                if(ReferenceEquals(obj, this)) return true;
                var other = obj as PairOfTypes;
                return other != null && (first == other.first && second == other.second);
            }
        }

        private static readonly IDictionary<PairOfTypes, Func<object, object>> sf_Converters = new Dictionary<PairOfTypes, Func<object, object>>();

        private static readonly ParameterExpression convParameter = Expression.Parameter(typeof(object), "value");

        [MethodImpl(MethodImplOptions.Synchronized)]
        private static Func<object, object> GetConverterFrom(Type TargetType, object Source)
        {
            return TargetType.GetConverterFrom(Source != null ? Source.GetType() : typeof(object));
        }

        public static Func<object, object> GetConverterFrom(this Type TargetType, Type Source)
        {
            Func<object, object> res;
            var key = new PairOfTypes(Source, TargetType);
            lock(sf_Converters) if(!sf_Converters.TryGetValue(key, out res))
                {
                    var body = Expression.Convert(Expression.Convert(Expression.Convert(convParameter, Source), TargetType), typeof(object));
                    res = Expression.Lambda<Func<object, object>>(body, convParameter).Compile();
                    sf_Converters.Add(key, res);
                }
            return res;
        }

        public static object Cast(this Type type, object obj) { return GetConverterFrom(type, obj)(obj); }

        public static Expression GetCastExpression(this Type FromType, Type ToType, ref ParameterExpression parameter)
        {
            if(parameter == null) parameter = Expression.Parameter(typeof(object), "value");
            return Expression.Convert(Expression.Convert(Expression.Convert(convParameter, FromType), ToType), typeof(object));
        }

        /// <summary>Получить тип по его имени из всех загруженных сборок</summary>
        /// <param name="TypeName">Имя типа</param>
        /// <returns>Тип</returns>
        [DebuggerStepThrough]
        public static Type GetType(string TypeName)
        {
            var lv_TypeArray = AppDomain.CurrentDomain.GetAssemblies().
                SelectMany((a, i) => a.GetTypes()).Where(t => (t.Name == TypeName)).ToArray();
            return lv_TypeArray.Length != 0 ? lv_TypeArray[0] : null;
        }

        [DebuggerStepThrough]
        public static TAttribute[] GetCustomAttributes<TAttribute>(this Type T)
            where TAttribute : Attribute
        {
            return GetCustomAttributes<TAttribute>(T, false);
        }

        [DebuggerStepThrough]
        public static TAttribute[] GetCustomAttributes<TAttribute>(this Type T, bool Inherited)
             where TAttribute : Attribute
        {
            return (from a in T.GetCustomAttributes(typeof(TAttribute), Inherited) select a as TAttribute).ToArray();

            //var A = T.GetCustomAttributes(typeof(TAttribute), Inherited).Select(o => o as TAttribute);
            //return new List<TAttribute>(A);
        }

        [DebuggerStepThrough]
        public static object CreateObject(this Type type)
        {

            //var lv_Info = type.GetConstructor(new Type[] { });
            //if(lv_Info == null)
            //    throw new InvalidOperationException("Не найден конструктор типа " +
            //        type + " без параметров. Для данного типа доступны следующие конструкторы " +
            //        type.GetConstructors().ConvertObjectTo(CInfo =>
            //        {
            //            if(CInfo.Length == 0) return "{}";
            //            var Result = "{" + CInfo[0].ToString();
            //            for(var i = 1; i < CInfo.Length; i++)
            //                Result += "; " + CInfo[i].ToString();
            //            return Result + "}";
            //        }));
            //return lv_Info.Invoke(new object[] { });
            Contract.Requires(type != null);
            return Activator.CreateInstance(type);
        }

        [DebuggerStepThrough]
        public static T Create<T>(this Type type) { return (T)type.CreateObject(); }

        [DebuggerStepThrough]
        public static object CreateObject(this Type type, params object[] Params)
        {
            //var ParamsTypes = Params == null ? new Type[] { } : Params.ConvertTo(o => o.GetType());
            //var Info = type.GetConstructor(ParamsTypes);
            //if(Info == null)
            //    throw new InvalidOperationException("У типа " + type + 
            //        " на найден конструктор с требуемым набором параметров " + 
            //        ParamsTypes.ConvertObjectTo(T =>
            //    {
            //        if(T.Length == 0) return "{}";
            //        var Result = "{" + T.ToString();
            //        for(var i = 1; i < T.Length; i++)
            //            Result += "; " + T[i].ToString();
            //        return Result + "}";
            //    }) +
            //    " Для данного типа доступны следующие конструкторы " +
            //        type.GetConstructors().ConvertObjectTo(CInfo =>
            //        {
            //            if(CInfo.Length == 0) return "{}";
            //            var Result = "{" + CInfo[0].ToString();
            //            for(var i = 1; i < CInfo.Length; i++)
            //                Result += "; " + CInfo[i].ToString();
            //            return Result + "}";
            //        }));
            //return Info.Invoke(Params);
            Contract.Requires(type != null);
            return Activator.CreateInstance(type, Params);
        }

        [DebuggerStepThrough]
        public static T Create<T>(params object[] Params) { return (T)CreateObject(typeof(T), Params); }
    }
}
